const { Lexer, TokenType, Token } = require('./lexer');

class Parser {
    constructor(lexer) {
        this.lexer = lexer;
        this.currentToken = null;
        this.peekToken = null;
        this.markCurrentToken = null;
        this.markPeekToken = null;
        this.nextToken();
        this.nextToken();
    }

    nextToken() {
        this.currentToken = this.peekToken;
        this.peekToken = this.lexer.nextToken();
    }

    parseProgram() {
        const program = [];
        while (this.currentToken.type !== TokenType.EOF) {
            const stmt = this.parseStatement();
            if (stmt) {
                program.push(stmt);
            }
            this.nextToken();
        }
        return program;
    }

    parseStatement() {
        switch (this.currentToken.type) {
            case TokenType.LET:
                return this.parseLetStatement();
            case TokenType.FUNCTION:
                return this.parseFunctionStatement();
            case TokenType.RETURN:
                return this.parseReturnStatement();
            case TokenType.IF:
                return this.parseIfStatement();
            case TokenType.FOR:
                return this.parseForStatement();
            case TokenType.WHILE:
                return this.parseWhileStatement();
            case TokenType.SWITCH:
                return this.parseSwitchStatement();

            case TokenType.PRINT:
                return this.parsePrintStatement();
            case TokenType.BREAK:
                return this.parseBreakStatement();
            default:
                return this.parseExpressionStatement();
        }
    }

    parseCaseStatement() {
        const stmt = {
            type: 'CaseStatement',
            token: this.currentToken,
            expression: null,
            isDefault: false,
            body: null,
            break: false
        };

        if (!this.expectPeek(TokenType.CASE)&&!this.expectPeek(TokenType.DEFAULT)) {
            return null;
        }
        if(this.currentTokenIs(TokenType.DEFAULT)){
            stmt.isDefault = true;
        }else{
            this.nextToken();
        }


        if(!stmt.isDefault){
            stmt.expression = this.parseExpression();
        }

        if (!this.expectPeek(TokenType.COLON)) {
            return null;
        }
        this.nextToken();
        if(this.currentTokenIs(TokenType.LBRACE)){
            stmt.body = this.parseBlockStatement();
        }else{
            stmt.body = this.parseStatement();
        }

        if(this.peekTokenIs(TokenType.BREAK)){
            this.nextToken();
            stmt.break = true;
            this.expectPeek(TokenType.SEMICOLON);
        }
        return stmt;



    }

    parseSwitchStatement() {
        const stmt = {
            type: 'SwitchStatement',
            token: this.currentToken,
            expression: null,
            cases: []
        };

        if (!this.expectPeek(TokenType.LPAREN)) {
            return null;
        }

        this.nextToken();

        stmt.expression = this.parseExpression();

        if (!this.expectPeek(TokenType.RPAREN)) {
            return null;
        }

        if (!this.expectPeek(TokenType.LBRACE)) {
            return null;
        }

        while (!this.peekTokenIs(TokenType.RBRACE) && !this.peekTokenIs(TokenType.EOF)) {
            const caseStmt = this.parseCaseStatement();
            if (caseStmt) {
                stmt.cases.push(caseStmt);
            }else{
                return null;
            }
        }

        if (!this.expectPeek(TokenType.RBRACE)) {
            return null;
        }

        return stmt;




    }

    parseBreakStatement() {
        const stmt = {
            type: 'BreakStatement',
            token: this.currentToken
        };

        if (this.peekTokenIs(TokenType.SEMICOLON)) {
            this.nextToken();
        }

        return stmt;
    }

    parseWhileStatement() {
        const stmt = {
            type: 'WhileStatement',
            token: this.currentToken,
            condition: null,
            body: null
        };

        if (!this.expectPeek(TokenType.LPAREN)) {
            return null;
        }

        this.nextToken();

        stmt.condition = this.parseExpression();
        if (!this.expectPeek(TokenType.RPAREN)) {
            return null;
        }
        if (!this.expectPeek(TokenType.LBRACE)) {
            return null;
        }
        stmt.body = this.parseBlockStatement();

        return stmt;
    }

    parseForStatement() {
        const stmt = {
            type: 'ForStatement',
            token: this.currentToken,
            init: null,
            condition: null,
            increment: null,
            body: null
        };

        if (!this.expectPeek(TokenType.LPAREN)) {
            return null;
        }

        this.nextToken();
        stmt.init = this.parseStatement();

        if (!this.expectCurrent(TokenType.SEMICOLON)) {
            return null;
        }

        stmt.condition = this.parseExpression();

        if (!this.expectPeek(TokenType.SEMICOLON)) {
            return null;
        }

        this.nextToken();
        stmt.increment = this.parseExpression();

        if (!this.expectPeek(TokenType.RPAREN)) {
            return null;
        }

        if (!this.expectPeek(TokenType.LBRACE)) {
            return null;
        }

        stmt.body = this.parseBlockStatement();

        return stmt;
    }

    parseIfStatement() {

        const stmt = {
            type: 'IfStatement',
            token: this.currentToken,
            condition: null,
            consequence: null,
            alternative: null
        };

        if (!this.expectPeek(TokenType.LPAREN)) {
            return null;
        }

        this.nextToken();
        stmt.condition = this.parseExpression();

        if (!this.expectPeek(TokenType.RPAREN)) {
            return null;
        }

        if (!this.expectPeek(TokenType.LBRACE)) {
            return null;
        }

        stmt.consequence = this.parseBlockStatement();

        if (this.peekTokenIs(TokenType.ELSE_IF)) {
            this.nextToken();
            stmt.alternative = this.parseIfStatement();
        } else if (this.peekTokenIs(TokenType.ELSE)) {
            this.nextToken();
            if (!this.expectPeek(TokenType.LBRACE)) {
                return null;
            }
            stmt.alternative = this.parseBlockStatement();
        }

        return stmt;
    }

    parseLetStatement() {
        const stmt = {
            type: 'LetStatement',
            token: this.currentToken,
            name: null,
            value: null
        };

        if (!this.expectPeek(TokenType.IDENT)) {
            return null;
        }

        stmt.name = {
            type: 'Identifier',
            token: this.currentToken,
            value: this.currentToken.literal
        };

        if (!this.expectPeek(TokenType.ASSIGN)) {
            return null;
        }

        this.nextToken();

        stmt.value = this.parseExpression();

        if (this.peekTokenIs(TokenType.SEMICOLON)) {
            this.nextToken();
        }

        return stmt;
    }

    parseFunctionStatement() {
        const stmt = {
            type: 'FunctionStatement',
            token: this.currentToken,
            name: null,
            parameters: [],
            body: null
        };

        if (!this.expectPeek(TokenType.IDENT)) {
            return null;
        }

        stmt.name = {
            type: 'Identifier',
            token: this.currentToken,
            value: this.currentToken.literal
        };

        if (!this.expectPeek(TokenType.LPAREN)) {
            return null;
        }

        stmt.parameters = this.parseFunctionParameters();

        if (!this.expectPeek(TokenType.LBRACE)) {
            return null;
        }

        stmt.body = this.parseBlockStatement();

        return stmt;
    }

    parseFunctionParameters() {
        const parameters = [];

        if (this.peekTokenIs(TokenType.RPAREN)) {
            this.nextToken();
            return parameters;
        }

        this.nextToken();

        parameters.push({
            type: 'Identifier',
            token: this.currentToken,
            value: this.currentToken.literal
        });

        while (this.peekTokenIs(TokenType.COMMA)) {
            this.nextToken();
            this.nextToken();
            parameters.push({
                type: 'Identifier',
                token: this.currentToken,
                value: this.currentToken.literal
            });
        }

        if (!this.expectPeek(TokenType.RPAREN)) {
            return null;
        }

        return parameters;
    }

    parseBlockStatement() {
        const block = {
            type: 'BlockStatement',
            statements: []
        };

        this.nextToken();

        while (!this.currentTokenIs(TokenType.RBRACE) && !this.currentTokenIs(TokenType.EOF)) {
            const stmt = this.parseStatement();
            if (stmt) {
                block.statements.push(stmt);
            }
            this.nextToken();
        }

        return block;
    }

    parseReturnStatement() {
        const stmt = {
            type: 'ReturnStatement',
            token: this.currentToken,
            returnValue: null
        };

        this.nextToken();

        stmt.returnValue = this.parseExpression();

        if (this.peekTokenIs(TokenType.SEMICOLON)) {
            this.nextToken();
        }

        return stmt;
    }

    parseExpressionStatement() {
        const stmt = {
            type: 'ExpressionStatement',
            expression: this.parseExpression()
        };

        if (this.peekTokenIs(TokenType.SEMICOLON)) {
            this.nextToken();
        }

        return stmt;
    }

    parseExpression(precedence = 0) {
        let leftExp;

        switch (this.currentToken.type) {
            case TokenType.IDENT:
                leftExp = this.parseIdentifier();
                break;
            case TokenType.INT:
                leftExp = this.parseIntegerLiteral();
                break;
            case TokenType.FLOAT:
                leftExp = this.parseFloatLiteral();
                break;
            case TokenType.TRUE:
            case TokenType.FALSE:
                leftExp = this.parseBooleanLiteral();
                break;
            case TokenType.LPAREN:
                leftExp = this.parseGroupedExpression();
                break;
            case TokenType.STRING:
                leftExp = this.parseStringLiteral();
                break;
            case TokenType.LBRACKET:
                leftExp = this.parseArrayLiteral();
                break;
            case TokenType.BITWISE_NOT:
            case TokenType.PLUS:
            case TokenType.MINUS:
            case TokenType.INCREMENT:
            case TokenType.DECREMENT:
            case TokenType.NOT:
                leftExp = this.parsePrefixExpression();
                break;
            case TokenType.LBRACE:
                leftExp = this.parseObjectLiteral();
                break;
            default:
                return null;
        }

        while (!this.peekTokenIs(TokenType.SEMICOLON) && precedence < this.peekPrecedence()) {
            switch (this.peekToken.type) {
                case TokenType.PLUS:
                case TokenType.MINUS:
                case TokenType.ASTERISK:
                case TokenType.SLASH:
                case TokenType.GT:
                case TokenType.LT:
                case TokenType.LE:
                case TokenType.GE:
                case TokenType.BITWISE_AND:
                case TokenType.BITWISE_OR:
                case TokenType.BITWISE_XOR:
                case TokenType.MODULO:
                case TokenType.SHIFT_LEFT:
                case TokenType.SHIFT_RIGHT:
                case TokenType.AND:
                case TokenType.OR:
                case TokenType.EQ:
                case TokenType.NOT_EQ:
                    this.nextToken();
                    leftExp = this.parseInfixExpression(leftExp);
                    break;
                case TokenType.LPAREN:
                    this.nextToken();
                    leftExp = this.parseCallExpression(leftExp);
                    break;
                case TokenType.LBRACKET:
                    this.nextToken();
                    leftExp = this.parseIndexExpression(leftExp);
                    break;
                case TokenType.ASSIGN:
                    this.nextToken();
                    leftExp = this.parseAssignExpression(leftExp);
                    break;
                case TokenType.DOT:
                    this.nextToken(); //
                    this.nextToken()//  consume '.'
                    const property = this.parseIdentifier();
                    leftExp = this.parsePropertyAccess(leftExp, property);
                    break;
                case TokenType.INCREMENT:
                case TokenType.DECREMENT:
                    this.nextToken();
                    leftExp = this.parseSuffixExpression(leftExp);
                    break;
                default:
                    return leftExp;
            }
        }

        return leftExp;
    }

    parseObjectLiteral() {
        const object = {
            type: 'ObjectLiteral',
            properties: []
        };

        while (!this.peekTokenIs(TokenType.RBRACE)) {
            this.nextToken(); // consume '{' or ','

            const key = this.parseExpression();
            if (!this.expectPeek(TokenType.COLON)) {
                return null;
            }

            this.nextToken(); // consume ':'
            const value = this.parseExpression();

            object.properties.push({ key, value });

            if (!this.peekTokenIs(TokenType.COMMA) && !this.peekTokenIs(TokenType.RBRACE)) {
                return null;
            }else{
                if(this.peekTokenIs(TokenType.COMMA)){
                    this.nextToken();
                }
            }
        }

        if (!this.expectPeek(TokenType.RBRACE)) {
            return null;
        }

        return object;
    }

    parseLambdaExpression() {
        const expression = {
            type: 'LambdaExpression',
            parameters: [],
            body: null
        };

        this.nextToken(); // consume '('

        while (!this.currentTokenIs(TokenType.RPAREN)&&!this.peekTokenIs(TokenType.EOF)) {
            expression.parameters.push(this.parseIdentifier());
            if (this.peekTokenIs(TokenType.COMMA)) {
                this.nextToken();
            }
            this.nextToken();
        }

        if (!this.expectPeek(TokenType.ARROW)) {
            return null;
        }

        this.nextToken(); // consume '->'

        expression.body = this.parseBlockStatement();

        return expression;
    }

    parsePrefixExpression() {
        const expression = {
            type: 'PrefixExpression',
            token: this.currentToken,
            operator: this.currentToken.literal,
            right: null
        };

        this.nextToken();
        expression.right = this.parseExpression(4); // 4 is the precedence for BITWISE_NOT

        return expression;
    }

    parseSuffixExpression(left) {
        return  {
            type: 'SuffixExpression',
            token: this.currentToken,
            operator: this.currentToken.literal,
            left: left
        };

    }

    parsePropertyAccess(object, property) {
        return {
            type: 'PropertyAccessExpression',
            object: object,
            property: property,
        };
    }

    parseAssignExpression(left) {
        if (left.type !== 'Identifier' && left.type !== 'IndexExpression'&& left.type !== 'PropertyAccessExpression') {
            throw new Error('Left side of assignment must be an identifier or index expression');
        }

        const exp = {
            type: 'AssignExpression',
            name: left,
            value: null
        };

        this.nextToken();
        exp.value = this.parseExpression();

        return exp;
    }

    parseIndexExpression(left) {
        const exp = {
            type: 'IndexExpression',
            left: left,
            index: null
        };

        this.nextToken(); // consume '['
        exp.index = this.parseExpression();

        if (!this.expectPeek(TokenType.RBRACKET)) {
            return null;
        }

        return exp;
    }

    parseStringLiteral() {
        return {
            type: 'StringLiteral',
            token: this.currentToken,
            value: this.currentToken.literal
        };
    }

    parseBooleanLiteral() {
        return {
            type: 'BooleanLiteral',
            token: this.currentToken,
            value: this.currentToken.type === TokenType.TRUE
        };
    }

    parseIdentifier() {
        const ident = {
            type: 'Identifier',
            token: this.currentToken,
            value: this.currentToken.literal
        };

        if (this.peekTokenIs(TokenType.LPAREN)) {
            this.nextToken();
            return this.parseCallExpression(ident);
        }

        return ident;
    }

    parseIntegerLiteral() {
        return {
            type: 'IntegerLiteral',
            token: this.currentToken,
            value: parseInt(this.currentToken.literal, 10)
        };
    }

    parseFloatLiteral() {
        return {
            type: 'IntegerLiteral',
            token: this.currentToken,
            value: parseFloat(this.currentToken.literal)
        };
    }

    parseGroupedExpression() {
        this.mark();
        this.nextToken(); // consume '('

        const exp = this.parseExpression();
        let groupMatched = true;
        if (!this.expectPeek(TokenType.RPAREN)) {
            groupMatched = false;
        }
        if(this.peekTokenIs(TokenType.ARROW)||(!groupMatched&&this.peekTokenIs(TokenType.COMMA))) {
            this.reset();
            return this.parseLambdaExpression();
        }

        if(!groupMatched){
            return null;
        }
        return {
            type: 'GroupedExpression',
            expression: exp
        };
    }

    parseInfixExpression(left) {
        const expression = {
            type: 'InfixExpression',
            token: this.currentToken,
            operator: this.currentToken.literal,
            left: left,
            right: null
        };

        const precedence = this.currentPrecedence();
        this.nextToken();
        expression.right = this.parseExpression(precedence);

        return expression;
    }

    parseCallExpression(func) {
        const call = {
            type: 'CallExpression',
            function: func,
            arguments: []
        };

        call.arguments = this.parseExpressionList(TokenType.RPAREN);
        return call;
    }

    parseExpressionList(endToken) {
        const list = [];

        if (this.peekTokenIs(endToken)) {
            this.nextToken();
            return list;
        }

        this.nextToken();
        list.push(this.parseExpression());

        while (this.peekTokenIs(TokenType.COMMA)) {
            this.nextToken();
            this.nextToken();
            list.push(this.parseExpression());
        }

        if (!this.expectPeek(endToken)) {
            return null;
        }

        return list;
    }

    parsePrintStatement() {
        const stmt = {
            type: 'PrintStatement',
            token: this.currentToken,
            arguments: []
        };

        // consume 'print'

        if (!this.expectPeek(TokenType.LPAREN)) {
            return null;
        }


        stmt.arguments = this.parseExpressionList(TokenType.RPAREN);

        if (this.peekTokenIs(TokenType.SEMICOLON)) {
            this.nextToken();
        }

        return stmt;
    }

    parseArrayLiteral() {
        const array = {
            type: 'ArrayLiteral',
            elements: []
        };

        if (this.peekTokenIs(TokenType.RBRACKET)) {
            this.nextToken();
            return array;
        }

        this.nextToken();
        array.elements.push(this.parseExpression());

        while (this.peekTokenIs(TokenType.COMMA)) {
            this.nextToken();
            this.nextToken();
            array.elements.push(this.parseExpression());
        }

        if (!this.expectPeek(TokenType.RBRACKET)) {
            return null;
        }

        return array;
    }

    expectCurrent(type) {
        if (this.currentTokenIs(type)) {
            this.nextToken();
            return true;
        } else {
            return false;
        }
    }


    expectPeek(type) {
        if (this.peekTokenIs(type)) {
            this.nextToken();
            return true;
        } else {
            return false;
        }
    }

    peekTokenIs(type) {
        return this.peekToken.type === type;
    }

    currentTokenIs(type) {
        return this.currentToken.type === type;
    }

    currentPrecedence() {
        return this.getPrecedence(this.currentToken.type);
    }

    peekPrecedence() {
        return this.getPrecedence(this.peekToken.type);
    }

    getPrecedence(tokenType) {
        const precedences = {
            [TokenType.ASSIGN]: 1,
            [TokenType.PLUS]: 2,
            [TokenType.MINUS]: 2,
            [TokenType.ASTERISK]: 3,
            [TokenType.SLASH]: 3,
            [TokenType.GT]: 1,
            [TokenType.LT]: 1,
            [TokenType.LE]: 1,
            [TokenType.GE]: 1,
            [TokenType.LBRACKET]: 5,
            [TokenType.DOT]: 6,
            [TokenType.BITWISE_AND]: 4,
            [TokenType.BITWISE_OR]: 4,
            [TokenType.BITWISE_XOR]: 4,
            [TokenType.BITWISE_NOT]: 4,
            [TokenType.MODULO]: 3,
            [TokenType.SHIFT_LEFT]: 3,
            [TokenType.SHIFT_RIGHT]: 3,
            [TokenType.EQ]: 1,
            [TokenType.INCREMENT]:2,
            [TokenType.DECREMENT]:2,
            [TokenType.NOT_EQ]: 1,
            [TokenType.AND]: 1,
            [TokenType.OR]: 1,
        };

        return precedences[tokenType] || 0;
    }

    mark() {
        this.lexer.mark();
        this.markCurrentToken=this.currentToken;
        this.markPeekToken=this.peekToken;
    }

    reset() {
        this.lexer.reset();
        this.currentToken=this.markCurrentToken;
        this.peekToken=this.markPeekToken;
    }


}

module.exports = Parser;
